#pragma once

#include "CoreMinimal.h"
#include "UVisDataProvider.h"
#include "Components/ActorComponent.h"
#include "UVisDataSubscriber.generated.h"

/// <summary>
/// Actual Data Subscriber implementation (internal use only)
/// </summary>
/// <typeparam name="DataT">Type of data</typeparam>
template<typename DataT, typename CallbackEventT>
requires std::derived_from<CallbackEventT, TMulticastDelegate<void(DataT)>>
class SIMUVISUALIZER_API UTypedDataSubscriber_Internal
{
public:
	~UTypedDataSubscriber_Internal();
	bool BindProvider(CallbackEventT* callback);
	bool HasDataUpdated() const { return bHasNewData_; }
	DataT GetUpdatedData() { bHasNewData_ = false; return Data_; }
protected:
	void UpdateData(DataT data) { Data_ = data; bHasNewData_ = true; }
protected:
	DataT Data_;
	bool bHasNewData_ = false;
	CallbackEventT* ProviderCallback_;
};

///////// Data Subscriber for Both C++ and BPs:
UCLASS(Blueprintable)
class SIMUVISUALIZER_API UFloatDataSubscriber : public UObject
{
	GENERATED_BODY()
public:
	//UFUNCTION(BlueprintCallable, Category = "Subscriber")
	//void UpdateData(float data) { TypedSubscriber_.UpdateData(data); }
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	bool HasDataUpdated() const { return TypedSubscriber_.HasDataUpdated(); }
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	float GetUpdatedData() { return TypedSubscriber_.GetUpdatedData(); }

	UTypedDataSubscriber_Internal<float, FOnFloatProvided>& GetInternalSubscriber() { return TypedSubscriber_; }

protected:
	UTypedDataSubscriber_Internal<float, FOnFloatProvided> TypedSubscriber_;
};

UCLASS(Blueprintable)
class SIMUVISUALIZER_API UIntDataSubscriber : public UObject
{
	GENERATED_BODY()
public:
	//UFUNCTION(BlueprintCallable, Category = "Subscriber")
	//void UpdateData(int data) { TypedSubscriber_.UpdateData(data); }
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	bool HasDataUpdated() const { return TypedSubscriber_.HasDataUpdated(); }
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	int GetUpdatedData() { return TypedSubscriber_.GetUpdatedData(); }

	UTypedDataSubscriber_Internal<int, FOnIntProvided>& GetInternalSubscriber() { return TypedSubscriber_; }

protected:
	UTypedDataSubscriber_Internal<int, FOnIntProvided> TypedSubscriber_;
};

UCLASS(Blueprintable)
class SIMUVISUALIZER_API UBoolDataSubscriber : public UObject
{
	GENERATED_BODY()
public:
	//UFUNCTION(BlueprintCallable, Category = "Subscriber")
	//void UpdateData(bool data) { TypedSubscriber_.UpdateData(data); }
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	bool HasDataUpdated() const { return TypedSubscriber_.HasDataUpdated(); }
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	bool GetUpdatedData() { return TypedSubscriber_.GetUpdatedData(); }

	UTypedDataSubscriber_Internal<bool, FOnBoolProvided>& GetInternalSubscriber() { return TypedSubscriber_; }

protected:
	UTypedDataSubscriber_Internal<bool, FOnBoolProvided> TypedSubscriber_;
};

UCLASS(Blueprintable)
class SIMUVISUALIZER_API UStringDataSubscriber : public UObject
{
	GENERATED_BODY()
public:
	//UFUNCTION(BlueprintCallable, Category = "Subscriber")
	//void UpdateData(FString data) { TypedSubscriber_.UpdateData(data); }
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	bool HasDataUpdated() const { return TypedSubscriber_.HasDataUpdated(); }
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	FString GetUpdatedData() { return TypedSubscriber_.GetUpdatedData(); }

	UTypedDataSubscriber_Internal<FString, FOnStringProvided>& GetInternalSubscriber() { return TypedSubscriber_; }

protected:
	UTypedDataSubscriber_Internal<FString, FOnStringProvided> TypedSubscriber_;
};
///////// end Data Subscriber for Both C++ and BPs ////////

template<std::derived_from<UObject> SubscriberT>
class SIMUVISUALIZER_API FDataSubscriberContainer
{
public:
	SubscriberT* GetSubscriberByIndex(int index);
protected:
	TArray<SubscriberT*> Subscribers_;
};

/// <summary>
/// Subscriber class for general use.(Cannot be used in BP)
/// </summary>
class SIMUVISUALIZER_API UVisDataSubscriber
{
public:
	UFloatDataSubscriber* GetFloatSubscriberByIndex(int index);
	UIntDataSubscriber* GetIntSubscriberByIndex(int index);
	UBoolDataSubscriber* GetBoolSubscriberByIndex(int index);
	UStringDataSubscriber* GetStringSubscriberByIndex(int index);

protected:
	TUniquePtr<FDataSubscriberContainer<UFloatDataSubscriber>> FloatSubscribers_;
	TUniquePtr<FDataSubscriberContainer<UIntDataSubscriber>> IntSubscribers_;
	TUniquePtr<FDataSubscriberContainer<UBoolDataSubscriber>> BoolSubscribers_;
	TUniquePtr<FDataSubscriberContainer<UStringDataSubscriber>> StringSubscribers_;
};

/// <summary>
/// Subscriber class for actors.
/// </summary>
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class UVisDataSubscriberComponent : public UActorComponent
{
	GENERATED_BODY()

public: 
	UVisDataSubscriberComponent(){}
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	UFloatDataSubscriber* GetFloatSubscriberByIndex(int index);
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	UIntDataSubscriber* GetIntSubscriberByIndex(int index);
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	UBoolDataSubscriber* GetBoolSubscriberByIndex(int index);
	UFUNCTION(BlueprintCallable, Category = "Subscriber")
	UStringDataSubscriber* GetStringSubscriberByIndex(int index);

	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
protected:	
	virtual void BeginPlay() override;
protected:
	UVisDataSubscriber DataSubscriber_;
};

template<typename DataT, typename CallbackEventT>
	requires std::derived_from<CallbackEventT, TMulticastDelegate<void(DataT)>>
inline UTypedDataSubscriber_Internal<DataT, CallbackEventT>::~UTypedDataSubscriber_Internal()
{
	if (ProviderCallback_ != nullptr)
	{
		ProviderCallback_->RemoveAll(this);
	}
	ProviderCallback_ = nullptr;
}

template<typename DataT, typename CallbackEventT>
	requires std::derived_from<CallbackEventT, TMulticastDelegate<void(DataT)>>
inline bool UTypedDataSubscriber_Internal<DataT, CallbackEventT>::BindProvider(CallbackEventT* callback)
{
	if (ProviderCallback_ != nullptr)
	{
		ProviderCallback_->RemoveAll(this);
	}
	ProviderCallback_ = nullptr;
	ProviderCallback_ = callback;
	ProviderCallback_->AddRaw(this, &UTypedDataSubscriber_Internal<DataT, CallbackEventT>::UpdateData);
	return false;
}

